//GA mpi
//$Id: ga_mpi.h 140 2009-09-03 11:33:57Z Oleg.Bulychov $
#pragma once
#ifndef GA_MPI_H
#define GA_MPI_H

#include "src/heo/include/easy_mpi.h"

#include "ga_common.h"
//__________________________________________________________________________________
/**
The Island Model with Ring Migration
*/
template<class P, class S, class C>
class GA_MPI:
    public GA<P, S, C>
{
public:
    typedef P TProblem;
    typedef S TSolution;
    typedef C TConfig;
    typedef typename S::TDirection TDirection;
    typedef typename S::TCostType  TCostType;

    static void solve(TProblem const& problem, TSolution& sol, TConfig const& cfg,
        mpi::communicator& comm)
    {
        int rank = comm.rank();
        int np   = comm.size();
        int prev = (np + rank - 1) % np;
        int next = (rank + 1)      % np;

        uniform_random rnd(0);

        TSolution tmp;
        tmp.init(problem, cfg);

        for (int N = 1; N <= cfg.run_count_; ++N)
        {
            GA_MPI<TProblem, TSolution, TConfig> ga;
            ga.init(problem, cfg);
            ga.generate_Random_Population(problem, cfg, rnd);
            ga.evaluate_Parents(problem);
            for (int step = 1; step <= cfg.max_step_; ++step)
            {
                ga.select_Parents(cfg, rnd);
                ga.crossover(cfg, rnd);

                //:reduce mutation level by time
                double m = cfg.mutation_probability_;
                if (cfg.mutation_fadeout_)
                    m *= (exp(exp(4. * log(1. - (step - 1) / cfg.max_step_))) - 1.);
                ga.mutation(m, rnd);
                ga.evaluate_Offsprings(problem);
                ga.select_Offsprings(cfg, rnd);
                ga.update_Solution(sol, cfg, step, rank);
                ring_migration<mpi::recv, mpi::send, mpi::isend>::migrate(ga, rank, prev,
                    next, np, tmp, cfg, rnd, step, comm);
            }
            ga.clear_MPI(rank, prev, tmp, cfg, comm);
            if (0 == rank)
            {
                if (cfg.debug_level_)
                    std::cout << "0: sol = " << sol.get_Fitness() << lf;
                for (int i = 1; i < np; ++i)
                {
                    mpi::recv(i, comm) >> tmp;
                    if (TDirection()(tmp.get_Fitness(), sol.get_Fitness()))
                    {//:better solution
                        if (cfg.debug_level_)
                            std::cout << i << ":*sol = " << tmp.get_Fitness() << lf;
                        sol.assign(tmp);
                    }
                    else
                    {
                        if (cfg.debug_level_)
                            std::cout << i << ": sol = " << tmp.get_Fitness() << lf;
                    }
                }
            }
            else
            {
                mpi::send(0, comm) << sol;
            }
        }
    }

private:

    void clear_MPI(int rank, int prev, TSolution& tmp, TConfig const& cfg,
        mpi::communicator& comm)
    {
        comm.barrier();
        if (!cfg.async_mode_)
            return;
        //std::cout << rank << ":enter clear_MPI(...)" << std::endl;
        mpi::recv in(prev, comm);
        while (in.is_Data_Available(0))
        {
            //__debugbreak();
            //std::cout << rank << ":***zombi-migrant(s) killed" << std::endl;
            for (int i = 0; i < cfg.migration_size_; ++i)
            {
                in >> tmp;
            }
        }
        comm.barrier();
        //std::cout << rank << ":leave clear_MPI(...)" << std::endl;
    }

};
//__________________________________________________________________________________
//__________________________________________________________________________________

#endif
//EOF!
